/**
 * 
 */
package neptune.manager.builder;

import java.util.Random;

import neptune.manager.NeptuneManager;
import neptune.manager.model.topology.MACAddress;
import neptune.manager.model.topology.NetworkInterface;
import neptune.manager.model.topology.Node;
import neptune.manager.model.topology.Topology;
import neptune.manager.storage.ExperimentRepositoryException;
import neptune.manager.storage.StorageReadException;
import neptune.manager.storage.TopologyDefinitionException;
import neptune.manager.utils.DebugLog;
import neptune.manager.utils.Logger.LogContext;
import neptune.manager.utils.Logger.MessageType;

/**
 * Classe di utilita' per la generazione di un MACAddress. I MAC address
 * generati sono garantiti come unici fra tutti gli esperimenti definiti sul
 * sistema.
 * 
 * @author Roberto Bifulco [RobertoBifulco.it] [info@robertoBifulco.it]
 * 
 */
public class MACAddressBuilder {

	private static String[] hexCyphers = new String[] { "0", "1", "2", "3",
			"4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" };
	private static String[] hexSecondByteAllowedCyphers = new String[] { "2",
			"6", "A", "E" };

	/**
	 * Genera un mac address unico in tutto il sistema. Per verificare
	 * l'univocita' e' necessario passare al metodo il repository degli
	 * esperimenti e la factory per gli storage delle topologie. Poiche' il
	 * metodo va a leggere questi dati, possono essere sollevate tutte le
	 * eccezioni derivanti da questi repository.
	 * 
	 * @param expRepository
	 * @param topologyStorageFactory
	 * @return
	 * @throws ExperimentRepositoryException
	 * @throws StorageReadException
	 * @throws TopologyDefinitionException
	 */
	public static MACAddress buildMACAddress(Topology actual, LogContext log)
			throws ExperimentRepositoryException, StorageReadException,
			TopologyDefinitionException {

		DebugLog.printDetailedLog("Defining mac address",
				MACAddressBuilder.class.getSimpleName());
		log.addMessage("Defining mac address", MessageType.NORMAL);

		MACAddress mac = null;

		do {
			String macString = generateMAC((mac == null) ? null : mac
					.getAddress());
			DebugLog.printDetailedLog("Trying MAC address:" + macString,
					MACAddressBuilder.class.getSimpleName());
			log.addMessage("Trying MAC address:" + macString,
					MessageType.NORMAL);
			mac = new MACAddress(macString);
		} while (isMacAlreadyDefined(mac, actual));

		DebugLog.printDetailedLog("Final MAC address:" + mac,
				MACAddressBuilder.class.getSimpleName());
		log.addMessage("Final MAC address:" + mac, MessageType.NORMAL);

		return mac;
	}

	/**
	 * Genera un MAC address incrementando di una unita' il mac address passato
	 * come argomento, o genera un mac address casuale se l'argomento e' null.
	 * 
	 * @param startingMac
	 * @return
	 */
	public static String generateMAC(String startingMac) {
		if (startingMac == null)
			return generateRandomMAC();

		// TODO da realizzare per garantire la terminazione deterministica

		return generateRandomMAC();
	}

	/**
	 * 
	 * Verifica se il dato mac address esiste gia' sul sistema.
	 * 
	 * @param mac
	 * @param actual
	 *            la topologia per la quale si sta definendo il mac
	 * @return
	 * @throws ExperimentRepositoryException
	 * @throws StorageReadException
	 * @throws TopologyDefinitionException
	 */
	public static boolean isMacAlreadyDefined(MACAddress mac, Topology actual)
			throws ExperimentRepositoryException, StorageReadException,
			TopologyDefinitionException {
		DebugLog.printDetailedLog("Veryfing if mac address already exists:"
				+ mac, MACAddressBuilder.class.getSimpleName());

		String[] exps = NeptuneManager.instance().getExperimentsId();

		for (String expId : exps) {
			DebugLog.printDetailedLog("Analyzing exp:" + expId,
					MACAddressBuilder.class.getSimpleName());

			Topology topology = NeptuneManager.instance().getExperiment(expId)
					.getTopology();

			for (Node node : topology.getNodes()) {

				DebugLog.printDetailedLog("node:" + node.getId(),
						MACAddressBuilder.class.getSimpleName());

				for (NetworkInterface netIf : node.getInterfaces()) {

					DebugLog.printDetailedLog("network interface:"
							+ netIf.getInterfaceName(), MACAddressBuilder.class
							.getSimpleName());

					if (mac.equals(netIf.getMacAddress())) {
						DebugLog.printDetailedLog("Mac address already exists:"
								+ mac, MACAddressBuilder.class.getSimpleName());
						return true;
					}
				}
			}

			for (Node node : actual.getNodes()) {

				DebugLog.printDetailedLog("node:" + node.getId(),
						MACAddressBuilder.class.getSimpleName());

				for (NetworkInterface netIf : node.getInterfaces()) {

					DebugLog.printDetailedLog("network interface:"
							+ netIf.getInterfaceName(), MACAddressBuilder.class
							.getSimpleName());

					if (mac.equals(netIf.getMacAddress())) {
						DebugLog.printDetailedLog("Mac address already exists:"
								+ mac, MACAddressBuilder.class.getSimpleName());
						return true;
					}
				}
			}
		}

		DebugLog.printDetailedLog("Mac address doesn't exists:" + mac,
				MACAddressBuilder.class.getSimpleName());

		return false;
	}

	/**
	 * Genera un MAC address casuale, senza verificarne l'unicita'.
	 * 
	 * @return
	 */
	public static String generateRandomMAC() {

		String[] address = new String[12];
		Random rand = new Random();
		for (int i = 0; i < address.length; i++) {
			int index = rand.nextInt(hexCyphers.length);

			address[i] = hexCyphers[index];
		}

		int index = rand.nextInt(hexSecondByteAllowedCyphers.length);
		address[1] = hexSecondByteAllowedCyphers[index];

		String composedAddress = "";
		for (int i = 0; i < address.length; i++) {
			composedAddress += address[i];
			if (i % 2 != 0 && i != address.length - 1)
				composedAddress += ":";
		}
		return composedAddress;
	}

}
